﻿#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable:6031)

#include <stdio.h>

//本章重点
//1. 为什么使用文件
//2. 什么是文件
//3. 文件的打开和关闭
//4. 文件的顺序读写
//5. 文件的随机读写
//6. 文本文件和二进制文件
//7. 文件读取结束的判定
//8. 文件缓冲区

/*1. 为什么使用文件*/
//我们前面学习结构体时，写通讯录的程序，当通讯录运行起来的时候，可以给通讯录中增加、删除数据，此时数据是存放在内存中，
//当程序退出的时候，通讯录中的数据自然就不存在了，等下次运行通讯录程序的时候，数据又得重新录入，如果使用这样的通讯录就很难受。
//我们在想既然是通讯录就应该把信息记录下来，只有我们自己选择删除数据的时候，数据才不复存在。
//这就涉及到了数据持久化的问题，我们一般数据持久化的方法有，把数据存放在磁盘文件、存放到数据库等方式。
//使用文件我们可以将数据直接存放在电脑的硬盘上，做到了数据的持久化。

/*2. 什么是文件*/
//磁盘上的文件是文件。但是在程序设计中，我们一般谈的文件有两种：程序文件、数据文件（从文件功能的角度来分类的）。

//2.1 程序文件
//包括源程序文件（后缀为.c）, 目标文件（windows环境后缀为.obj）, 可执行程序（windows环境后缀为.exe）。

//2.2 数据文件
//文件的内容不一定是程序，而是程序运行时读写的数据，比如程序运行需要从中读取数据的文件，或者输出内容的文件。
//本章讨论的是数据文件。
//在以前各章所处理数据的输入输出都是以终端为对象的，即从终端的键盘输入数据，运行结果显示到显示器上。
//其实有时候我们会把信息输出到磁盘上，当需要的时候再从磁盘上把数据读取到内存中使用，这里处理的就是磁盘上文件。

//2.3 文件名
//一个文件要有一个唯一的文件标识，以便用户识别和引用。
//文件名包含3部分：文件路径 + 文件名主干 + 文件后缀
//例如： c : \code\test.txt		文件路径c : \code\  文件名主干test  文件后缀.txt
//为了方便起见，文件标识常被称为文件名。


/* 3. 文件的打开和关闭*/

//3.1 文件指针
//缓冲文件系统中，关键的概念是“文件类型指针”，简称“文件指针”。
//每个被使用的文件都会在内存中开辟了一个相应的文件信息区，用来存放文件的相关信息（如文件的名字，文件状态及文件当前的位置等）。
//这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的，取名FILE.

//例如，VS2013编译环境提供的 stdio.h 头文件中有以下的文件类型申明：
//typedef struct _iobuf {
//	char* _ptr;
//	int   _cnt;
//	char* _base;
//	int   _flag;
//	int   _file;
//	int   _charbuf;
//	int   _bufsiz;
//	char* _tmpfname;
//}FILE;

//每当打开一个文件的时候，系统会根据文件的情况自动创建一个FILE结构的变量，并填充其中的信息
//一般都是通过一个FILE的指针来维护这个FILE结构的变量，这样使用起来更加方便。
//FILE* pf;//文件指针变量
//定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区（是一个结构体变量）。
//通过该文件信息区中的信息就能够访问该文件。也就是说，通过文件指针变量能够找到与它关联的文件。

//3.2 文件的打开和关闭
//文件在读写之前应该先打开文件，在使用结束之后应该关闭文件。
//在编写程序的时候，在打开文件的同时，都会返回一个FILE* 的指针变量指向该文件，也相当于建立了指针和文件的关系。
//ANSIC 规定使用fopen函数来打开文件，fclose来关闭文件。
//打开文件
//FILE* fopen(const char* filename, const char* mode);		//参数为文件名，文件的打开模式（读、写...）
////关闭文件
//int fclose(FILE* stream);

/* fopen fclose example */
#include <string.h>
#include <errno.h>
//int main()
//{
//	//打开文件
//	FILE* pf = fopen("myfile.txt", "r");		//fopen打开文件失败的话会返回一个空指针；用相对路径打开文件
//	//FILE* pf = fopen("C:\\Users\\13765\\Desktop\\mylife.txt", "r");		//用绝对路径打开文件，前面的\将后面的\转化为普通的\
//
//	if (pf == NULL)
//	{
//		printf("%s\n", strerror(errno));
//		return 1;
//	}
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

/*********  读（输入）：获取文件的内容读到内存中
            写（输出）：在内存中写数据存储到文件中  **********/


/* 4. 文件的顺序读写 */
//4.1 顺序读写函数介绍
// 写文件（写一个字符）
//int main()
//{
//	//打开文件
//	FILE* pf = fopen("myfile.txt", "w");	
//	if (pf == NULL)
//	{
//		printf("%s\n", strerror(errno));
//		return 1;
//	}
//	//写文件
//	char i = 0;
//	for (i = 'a'; i <= 'z'; i++)
//	{
//		fputc(i, pf);		//将字符i写到myfile.txt文件里
//	}
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//写一行
//int main()
//{
//	//打开文件
//	FILE* pf = fopen("myfile.txt", "w");	
//	if (pf == NULL)
//	{
//		printf("%s\n", strerror(errno));
//		return 1;
//	}
//	//写一行
//	fputs("hello world", pf);		//将一行字符写到myfile.txt文件里；若之前有内容就将之前的内容销毁再写。将w改成a时就是在原有字符基础上追加新的内容
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//读文件（读一个字符）
//int main()
//{
//	//打开文件
//	FILE* pf = fopen("myfile.txt", "r");
//	if (pf == NULL)
//	{
//		printf("%s\n", strerror(errno));
//		return 1;
//	}
//	//读文件
//	//int ch = fgetc(pf);		//将pf指向的文件中读一个字符放到ch中
//	//printf("%c\n", ch);		//文本中存的是abcd....,第一次读就是a，下一次就是b.....
//	//ch = fgetc(pf);
//	//printf("%c\n", ch);
//	//ch = fgetc(pf);
//	//printf("%c\n", ch);
//	//ch = fgetc(pf);
//	//printf("%c\n", ch);
//	//char i = 0;
//	int ch = 0;
//	while ( (ch=fgetc(pf)) != EOF )
//	{
//		printf("%c ", ch);
//	}
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//读一行
//int main()
//{
//	//打开文件
//	FILE* pf = fopen("myfile.txt", "r");
//	if (pf == NULL)
//	{
//		//printf("%s\n", strerror(errno));
//		perror("fopen");	//打印错误信息，更简洁
//		return 1;
//	}
//	//读一行
//	char arr[20];
//	fgets(arr, 5, pf);		//读到的字符串会被放到指针里；注意写5但实际读到4个数据，因为在最后会默认读一个\0
//	printf("%s\n", arr);
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//写一个结构体：数据格式化输出函数fprintf 
//struct S
//{
//	char arr[10];
//	int age;
//	float score;
//};

//int main()
//{
//	struct S s = { "zhangsan",25,50.5f };
//	//打开文件
//	FILE* pf = fopen("myfile.txt", "w");
//	if (pf == NULL)
//	{
//		//printf("%s\n", strerror(errno));
//		perror("fopen");	//打印错误信息，更简洁
//		return 1;
//	}
//	//写
//	fprintf(pf,"%s %d %f",s.arr,s.age,s.score);	//将信息不是打印到屏幕上，而是写到文件中
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//读一个结构体
//int main()
//{
//	struct S s = {0};
//	//打开文件
//	FILE* pf = fopen("myfile.txt", "r");
//	if (pf == NULL)
//	{
//		//printf("%s\n", strerror(errno));
//		perror("fopen");	//打印错误信息，更简洁
//		return 1;
//	}
//	//读
//	fscanf(pf, "%s %d %f", s.arr, &(s.age), &(s.score));	//从pf指向的文件中读到s中
//	//printf("%s %d %f\n", s.arr, s.age, s.score);
//	fprintf(stdout, pf, "%s %d %f", s.arr, s.age, s.score); //用fprintf也可以将数据打印到屏幕上
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//任何一个C程序，只要运行起来就会默认打开3个流：
// File* stdin  -标准输入流（键盘）
// File* stdout -标准输出流（屏幕）
// File* stderr -标准错误流（屏幕）
//这三个流的类型都是File*的


//写
//struct S
//{
//	char arr[10];
//	int age;
//	float score;
//};

//int main()
//{
//	struct S s = { "zhangsan",25,50.5f };	
//	//打开文件
//	FILE* pf = fopen("myfile.txt", "wb");	//以二进制的形式写到文件中—用输出fwrite
//	if (pf == NULL)
//	{
//		//printf("%s\n", strerror(errno));
//		perror("fopen");	//打印错误信息，更简洁
//		return 1;
//	}
//	//写
//	fwrite(&s, sizeof(struct S),1,pf);	
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//读
//int main()
//{
//	struct S s = {0};
//	//打开文件
//	FILE* pf = fopen("myfile.txt", "rb");
//	if (pf == NULL)
//	{
//		perror("fopen");
//		return 1;
//	}
//	//读
//	fread(&s, sizeof(struct S), 1, pf);			//从流里面读count个大小为size的数据放到ptr的位置
//	printf("%s %d %f\n", s.arr, s.age, s.score);
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//4.2 对比一组函数：
//scanf / fscanf / sscanf
//printf / fprintf / sprintf

//scanf是针对标准输入的格式化输入语句
//printf是针对标准输出的格式化输出语句
//
//fscanf是针对所有输入流的格式化输入语句
//fprintf是针对所有输出流的格式化输出语句
// 
//sscanf从一个字符串中转化成一个格式化的数据
//sprintf把一个格式化的数据写到字符串中，本质是把一个格式化的数据转换成字符串
//struct S
//{
//	char arr[10];
//	int age;
//	float score;
//};
//
//int main()
//{
//	struct S s = { "zhangsan",20,50.5f };
//	struct S tmp = { 0 };
//	char buf[100] = { 0 };
//	sprintf(buf, "%s %d %f\n", s.arr, s.age, s.score);		//把s中格式化的数据s.arr, s.age, s.score按照%s %d %f这种格式转换成字符串放到buf中
//	printf("以字符串的形式打印：%s\n", buf);
//	sscanf(buf, "%s %d %f", tmp.arr, &(tmp.age), &(tmp.score));//从字符串buf中获取一个格式化的数据放到tmp中
//	printf("以格式化的数据打印：%s %d %f\n", tmp.arr, tmp.age, tmp.score);
//
//	return 0;
//}


/* 5. 文件的随机读写 */
//5.1 fseek 根据文件指针的位置和偏移量来定位文件指针。
//int fseek(FILE* stream, long int offset, int origin);

//int main()
//{
//	//打开文件
//	FILE* pf = fopen("myfile.txt", "r");
//	if (pf == NULL)
//	{
//		printf("%s\n", strerror(errno));
//		return 1;
//	}
//	//读文件
//	//定位文件指针
//	fseek(pf, 2, SEEK_SET);
//	int ch = fgetc(pf);		//c
//	printf("%c\n", ch);		//读完后文件指针会自动+1
//	printf("%d\n", ftell(pf));//3
//
//	fseek(pf, 2, SEEK_CUR);
//	ch = fgetc(pf);			//f
//	printf("%c\n", ch);
//
//	rewind(pf);
//
//	//关闭文件
//	fclose(pf);
//	pf = NULL;
//
//	return 0;
//}

//5.2 ftell 返回当前文件指针相对于起始位置的偏移量   long int ftell(FILE* stream);
//5.3 rewind 让文件指针的位置回到文件的起始位置  void rewind ( FILE * stream );


/* 6. 文本文件和二进制文件*/
//根据数据的组织形式，数据文件被称为文本文件或者二进制文件。
//数据在内存中以二进制的形式存储，不加转换的输出到外存，就是二进制文件。
//如果要求在外存上以ASCII码的形式存储，则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
//一个数据在内存中是怎么存储的呢？
//字符一律以ASCII形式存储，数值型数据既可以用ASCII形式存储，也可以使用二进制形式存储。
//如有整数10000，如果以ASCII码的形式输出到磁盘，则磁盘中占用5个字节（每个字符一个字节），
//而二进制形式输出，则在磁盘上只占4个字节（VS2013测试）。
//int main()
//{
//    int a = 10000;
//    FILE* pf = fopen("test.txt", "wb");//以二进制的形式写到文件中
//    fwrite(&a, 4, 1, pf);       //因为10000是个整数，整型数据在内存中占4个字节
//    fclose(pf);
//    pf = NULL;
//    return 0;
//}


/* 7. 文件读取结束的判定 */
//7.1 被错误使用的feof
//牢记：在文件读取过程中，不能用feof函数的返回值直接来判断文件的是否结束。
//feof 的作用是：当文件读取结束的时候，判断是读取结束的原因是遇到文件尾结束，还是读取失败结束。
//1. 文本文件读取是否结束，判断返回值是否为 EOF （ fgetc ），或者 NULL （ fgets ）
//例如：
//fgetc 判断是否为 EOF .
//fgets 判断返回值是否为 NULL .
//2. 二进制文件的读取（fread）结束判断，判断返回值是否小于实际要读的个数。
//例如：
//fread判断返回值是否小于实际要读的个数。
#include <stdlib.h>
//int main(void)
//{
//    int c; // 注意：int，非char，要求处理EOF
//    FILE* fp = fopen("test.txt", "r");
//    if (!fp) {
//        perror("File opening failed");
//        return EXIT_FAILURE;
//    }
//    //fgetc 当读取失败的时候或者遇到文件结束的时候，都会返回EOF
//    while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
//    {
//        putchar(c);
//    }
//    //判断是什么原因结束的
//    if (ferror(fp))
//        puts("I/O error when reading");
//    else if (feof(fp))
//        puts("End of file reached successfully");
//    fclose(fp);
//}

//enum { SIZE = 5 };
//int main(void)
//{
//    double a[SIZE] = { 1.,2.,3.,4.,5. };
//    FILE* fp = fopen("test.bin", "wb"); // 必须用二进制模式
//    fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组
//    fclose(fp);
//    double b[SIZE];
//    fp = fopen("test.bin", "rb");
//    size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组
//    if (ret_code == SIZE) {
//        puts("Array read successfully, contents: ");
//        for (int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
//        putchar('\n');
//    }
//    else { // error handling
//        if (feof(fp))
//            printf("Error reading test.bin: unexpected end of file\n");
//        else if (ferror(fp)) {
//            perror("Error reading test.bin");
//        }
//    }
//    fclose(fp);
//}


/* 8. 文件缓冲区 */
//ANSIC 标准采用“缓冲文件系统”处理的数据文件的，所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。
//从内存向磁盘输出数据会先送到内存中的缓冲区，装满缓冲区后才一起送到磁盘上。
//如果从磁盘向计算机读入数据，则从磁盘文件中读取数据输入到内存缓冲区（充满缓冲区），
//然后再从缓冲区逐个地将数据送到程序数据区（程序变量等）。缓冲区的大小根据C编译系统决定的。
#include <windows.h>
int main()
{
    FILE* pf = fopen("test.txt", "w");
    fputs("abcdef", pf);//先将代码放在输出缓冲区

    printf("睡眠10秒-已经写数据了，打开test.txt文件，发现文件没有内容\n");
    Sleep(10000);

    printf("刷新缓冲区\n");
    fflush(pf);//刷新缓冲区时，才将输出缓冲区的数据写到文件（磁盘）
    //注：fflush 在高版本的VS上不能使用了
    printf("再睡眠10秒-此时，再次打开test.txt文件，文件有内容了\n");
    Sleep(10000);
    fclose(pf);
    //注：fclose在关闭文件的时候，也会刷新缓冲区
    pf = NULL;
    return 0;
}